﻿// Copyright (c) 2011 João Angelo
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of this 
// software and associated documentation files (the "Software"), to deal in the Software 
// without restriction, including without limitation the rights to use, copy, modify, merge, 
// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons 
// to whom the Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all copies or 
// substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, 
// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR 
// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE 
// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 
// TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR 
// THE USE OR OTHER DEALINGS IN THE SOFTWARE.

namespace Helpers.Web
{
    using System;
    using System.CodeDom;
    using System.Collections.Generic;
    using System.ComponentModel;
    using System.Linq;
    using System.Web;
    using System.Web.Compilation;
    using System.Web.UI;

    [ExpressionPrefix("AssemblyResources")]
    public sealed class AssemblyResourceExpressionBuilder : ExpressionBuilder
    {
        public override object ParseExpression(
            string expression,
            Type propertyType,
            ExpressionBuilderContext context)
        {
            if (expression == null || String.IsNullOrWhiteSpace(expression))
                throw new HttpException(GetInvalidExpressionErrorMessage(expression));

            string[] expressions = expression.Split(
                new char[] { ';' },
                StringSplitOptions.RemoveEmptyEntries);

            var resourceExpressions = new List<ResourceExpression>();

            foreach (var e in expressions)
            {
                int lastCommaIndex = e.LastIndexOf(',');

                if (lastCommaIndex < 0)
                    throw new HttpException(GetInvalidExpressionErrorMessage(expression));

                string alias = e.Substring(0, lastCommaIndex).Trim();
                string key = e.Substring(lastCommaIndex + 1).Trim();

                var typeInfo = ResourceTypeAliasManager.GetResourceType(alias);

                if (typeInfo == null)
                    throw new HttpException(
                        String.Format("Unknown assembly resource alias ({0}).", alias));

                resourceExpressions.Add(new ResourceExpression(key, typeInfo));
            }

            return resourceExpressions.ToArray();
        }

        public override CodeExpression GetCodeExpression(
            BoundPropertyEntry entry,
            object parsedData,
            ExpressionBuilderContext context)
        {
            var resourceExpressions = (ResourceExpression[])parsedData;

            var codeArrayOfResourceExpressions = new CodeArrayCreateExpression(
                typeof(ResourceExpression),
                resourceExpressions.Select(re => GetObjectCreateExpression(re)).ToArray());

            var expression = new CodeMethodInvokeExpression();

            expression.Method = new CodeMethodReferenceExpression(
                new CodeTypeReferenceExpression(typeof(AssemblyResourceExpressionBuilder)),
                "EvaluateResourceExpressions");

            expression.Parameters.Add(codeArrayOfResourceExpressions);
            expression.Parameters.Add(new CodeTypeOfExpression(entry.DeclaringType));
            expression.Parameters.Add(new CodePrimitiveExpression(entry.PropertyInfo.Name));

            return expression;
        }

        public override object EvaluateExpression(
            object target, 
            BoundPropertyEntry entry, 
            object parsedData, 
            ExpressionBuilderContext context)
        {
            return EvaluateResourceExpressions(
                (ResourceExpression[])parsedData, 
                entry.DeclaringType, 
                entry.PropertyInfo.Name);
        }

        public static object EvaluateResourceExpressions(
            ResourceExpression[] expressions, 
            Type targetPropertyDeclaringType, 
            string targetPropertyName)
        {
            var provider = AssemblyResourceProvider.GetProvider(expressions[0].TypeInfo);

            if (expressions.Length == 1)
                return ConvertStringToPropertyType(
                    provider.GetObject(expressions[0].ResourceKey, null), 
                    targetPropertyDeclaringType, 
                    targetPropertyName);

            string format = (string)provider.GetObject(expressions[0].ResourceKey, null);

            object[] parameters = new object[expressions.Length - 1];

            for (int i = 1; i < expressions.Length; i++)
            {
                provider = AssemblyResourceProvider.GetProvider(expressions[i].TypeInfo);

                parameters[i - 1] = provider.GetObject(expressions[i].ResourceKey, null);
            }

            return ConvertStringToPropertyType(
                String.Format(format, parameters),
                targetPropertyDeclaringType,
                targetPropertyName);
        }

        private static CodeObjectCreateExpression GetObjectCreateExpression(
            ResourceExpression re)
        {
            var resourceTypeInfoCreateExpression = new CodeObjectCreateExpression(
                typeof(ResourceTypeInfo),
                new CodePrimitiveExpression(re.TypeInfo.AssemblyQualifiedTypeName));

            return new CodeObjectCreateExpression(
                typeof(ResourceExpression),
                new CodePrimitiveExpression(re.ResourceKey),
                resourceTypeInfoCreateExpression);
        }

        private static object ConvertStringToPropertyType(
            object value,
            Type propertyDeclaringType,
            string propertyName)
        {
            if (value == null)
                return value;

            string resultAsString = value as string;

            if (resultAsString == null)
                return value;

            var properties = TypeDescriptor.GetProperties(propertyDeclaringType);

            var propertyDescriptor = properties[propertyName];

            if (propertyDescriptor == null || propertyDescriptor.Converter == null)
                return value;

            return propertyDescriptor.Converter.ConvertFromInvariantString(resultAsString);
        }

        private static string GetInvalidExpressionErrorMessage(string expression)
        {
            return String.Format("Invalid assembly resource expression ({0}).", expression);
        }

        public override bool SupportsEvaluate
        {
            get { return true; }
        }
    }
}
